// **********************************************************************
// 
// <copyright>
// 
//  BBN Technologies
//  10 Moulton Street
//  Cambridge, MA 02138
//  (617) 873-8000
// 
//  Copyright (C) BBNT Solutions LLC. All rights reserved.
// 
// </copyright>
// **********************************************************************
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/layer/vpf/LibrarySelectionTable.java,v $
// $Revision: 1.13 $ $Date: 2005/12/09 21:08:57 $ $Author: dietrick $
// **********************************************************************

package com.bbn.openmap.layer.vpf;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

import com.bbn.openmap.io.BinaryFile;
import com.bbn.openmap.io.FormatException;
import com.bbn.openmap.proj.coords.LatLonPoint;

/**
 * Reads the VPF LibraryAttribute table and constructs CoverageAttributeTables
 * for each of the library coverages (north america, browse, etc) that exist.
 * 
 * <p>
 * NOTE: This class maintains a whole bunch of cached information, and also
 * hangs onto references to classes that cache even more information. When using
 * this class, you are much better off sharing an instance of this class, rather
 * than creating multiple instantiations of it for the same VPF data directory.
 * 
 * @see CoverageAttributeTable
 */
public class LibrarySelectionTable {

    /** cutoff scale for browse coverage. */
    public final static int DEFAULT_BROWSE_CUTOFF = 31000000;

    protected int BROWSE_CUTOFF = DEFAULT_BROWSE_CUTOFF;

    /**
     * the names of the VPF libraries listed in the library attribute table
     */
    // private String libraryname[] = null; //library [i]
    /** the bounding rectangle of the respective libraries */
    private Map<String, float[]> boundrec = new HashMap<String, float[]>();// bounding
                                                                           // rect
                                                                           // as
                                                                           // [W,S,E,N]
    /** the CoverageAttributeTables corresponding to the different libs */
    private Map<String, CoverageAttributeTable> CATs = new HashMap<String, CoverageAttributeTable>();
    /**
     * A list of libraries in the same order as their data paths were added.
     */
    private List<String> orderedLibraryNameList = new ArrayList<String>();

    /** the names of the lat columns */
    final private static String LATColumns[] = { Constants.LAT_LIBNAME, Constants.LAT_XMIN,
            Constants.LAT_YMIN, Constants.LAT_XMAX, Constants.LAT_YMAX };
    /** the expected schema types for the library attribute table */
    final private static char LATschematype[] = { 'T', 'F', 'F', 'F', 'F' };
    /** the expected schema lengths for the library attribute table */
    final private static int LATschemalength[] = { -1 /* 8 */, 1, 1, 1, 1 };
    /** the name of the database */
    private String databaseName;
    /** the database description of itself */
    private String databaseDesc;

    /**
     * Construct a LibrarySelectionTable without a path to data.
     */
    public LibrarySelectionTable() {
    }

    /**
     * Construct a LibrarySelectionTable with a path to data.
     * 
     * @param vpfpath the path to the base data directory; the file opened is
     *        <code>vpfpath</code> /lat.
     * @exception FormatException some error was encountered while trying to
     *            handle the file.
     */
    public LibrarySelectionTable(String vpfpath) throws FormatException {
        addDataPath(vpfpath);
    }

    /**
     * Construct a LibrarySelectionTable with a path to data.
     * 
     * @param vpfpaths the paths to the data directories; the file opened is
     *        <code>vpfpath</code> /lat.
     * @exception FormatException some error was encountered while trying to
     *            handle the file.
     */
    public LibrarySelectionTable(String vpfpaths[]) throws FormatException {
        for (int i = 0; i < vpfpaths.length; i++) {
            addDataPath(vpfpaths[i]);
        }
    }

    /**
     * Set the cutoff scale where if the map scale number is larger (smaller
     * overall map scale), the coverage won't be returned. For example, if the
     * scale cutoff is 30000000, if the map scale is 1:31000000, no map data
     * will be returned.
     */
    public void setCutoffScale(int scale) {
        BROWSE_CUTOFF = scale;
    }

    /**
     * Get the cutoff scale where data will be retrieved.
     */
    public int getCutoffScale() {
        return BROWSE_CUTOFF;
    }

    /**
     * add a path to LibrarySelectionTable. Adding different types of VPF
     * libraries to the same LST is likely to cause trouble. (e.g. it would be
     * bad to add both DCW and VMAP paths to the same LST. adding each DCW disk
     * separately is why this method exists.)
     * 
     * @param vpfpath the path to the base DCW directory; the file opened is
     *        <code>vpfpath</code> /lat.
     * @exception FormatException some error was encountered while trying to
     *            handle the file.
     */
    public void addDataPath(String vpfpath) throws FormatException {
        VPFLayer.logger.fine("LST.addDataPath(" + vpfpath + ")");

        // Figure out how files names should be constructed...
        boolean addSlash = true;

        if (vpfpath.endsWith("/") || vpfpath.endsWith(File.separator)) {
            addSlash = false;
        }

        String latf = vpfpath + (addSlash ? "/" : "") + "lat";
        String dhtf = vpfpath + (addSlash ? "/" : "") + "dht";

        if (!BinaryFile.exists(latf)) {
            latf += ".";
            dhtf += ".";
        }

        DcwRecordFile latrf = new DcwRecordFile(latf);
        DcwRecordFile dhtrf = new DcwRecordFile(dhtf);

        List<Object> databaseVec = dhtrf.parseRow();
        int dcol = dhtrf.whatColumn("database_name");
        if (dcol != -1) {
            databaseName = (String) databaseVec.get(dcol);
        }
        dcol = dhtrf.whatColumn("database_desc");
        if (dcol != -1) {
            databaseDesc = (String) databaseVec.get(dcol);
        }
        dhtrf.close();
        dhtrf = null;

        int latcols[] = latrf.lookupSchema(LATColumns, true, LATschematype, LATschemalength, false);

		final List<String> libDirectories = searchLibraryDirectories(vpfpath);

		VPFLayer.logger.fine("lst.adp: looked up schema");
		for (final List l = new ArrayList(latrf.getColumnCount()); latrf.parseRow(l);) {
			final String lname = ((String) l.get(latcols[0])).toLowerCase();
			
			if (!libDirectories.contains(lname.toUpperCase())) {
				continue;
			}
			
			final float br[] = new float[] { ((Float) l.get(latcols[1])).floatValue(),
					((Float) l.get(latcols[2])).floatValue(), ((Float) l.get(latcols[3])).floatValue(),
					((Float) l.get(latcols[4])).floatValue() };
			try {
				CoverageAttributeTable table = new CoverageAttributeTable(vpfpath, lname);
				CATs.put(lname, table);
				boundrec.put(lname, br);
				orderedLibraryNameList.add(lname);
				if (VPFLayer.logger.isLoggable(Level.FINE)) {
					VPFLayer.logger.fine(lname + " " + br[0] + " " + br[1] + " " + br[2] + " " + br[3]);
				}
			} catch (FormatException fe) {
				if (VPFLayer.logger.isLoggable(Level.FINER)) {
					VPFLayer.logger.finer(
							"*****\nVPFLayer.LST: Couldn't create CoverageAttributeTable for " + vpfpath + " " + lname
									+ " " + fe.getMessage() + "\n--- Not a problem if you have multiple paths, and "
									+ lname + " is included in another path ---\n*****");
					fe.printStackTrace();
				} else {
					VPFLayer.logger
							.fine("VPFLayer.LST: CAT discrepancy (run with finer logging level for more details)");
				}
			}
		}
		latrf.close();
		latrf = null;
	}

	private List<String> searchLibraryDirectories(final String vpfpath) {
		final ArrayList<String> subDirLibWithCat = new ArrayList<String>();

		final File vpfDirectory = new File(vpfpath);
		final File[] vpfSubDirectory = vpfDirectory.listFiles(new FileFilter() {
			public boolean accept(final File file) {
				return file.isDirectory();
			}
		});

		for (int i = 0; i < vpfSubDirectory.length; i++) {
			final File subDir = vpfSubDirectory[i];
			String cat = subDir + "/cat";
			if (BinaryFile.exists(cat)) {
				subDirLibWithCat.add(subDir.getName().toUpperCase());
				continue;
			}
			cat = cat + ".";
			if (BinaryFile.exists(cat)) {
				subDirLibWithCat.add(subDir.getName().toUpperCase());
			}
		}

		return subDirLibWithCat;
	}

	/**
	 * Return the list of libraries that this database has.
	 * 
	 * @return the list of libraries. for DCW, this is typically NOAMER, BROWSE,
	 *         etc.
	 */
	public List<String> getLibraryNames() {
		return new ArrayList<String>(orderedLibraryNameList);
	}

    /**
     * Return the name of the database we are reading from.
     */
    public String getDatabaseName() {
        return databaseName;
    }

    /**
     * Return the description of the database we are reading from.
     */
    public String getDatabaseDescription() {
        return databaseDesc;
    }

    /**
     * Return the coverage attribute table (list of coverages available for the
     * given library) for the given library name.
     * 
     * @param library the name of the library to get the CAT for
     * @return the CoverageAttributeTable requested (null if the library
     *         requested doesn't exist in the database)
     * @exception FormatException exceptions from opening the CAT for the
     *            library
     */
    public CoverageAttributeTable getCAT(String library) throws FormatException {
        return CATs.get(library);
    }

    /**
     *  
     */
    public void drawTile(int scale, int screenwidth, int screenheight, String covname,
                         VPFGraphicWarehouse warehouse, LatLonPoint ll1, LatLonPoint ll2) {

        if (VPFLayer.logger.isLoggable(Level.FINE)) {
            VPFLayer.logger.fine("Library selection table coverage: " + covname);
            VPFLayer.logger.fine("Library selection table - edges: " + warehouse.drawEdgeFeatures());
            VPFLayer.logger.fine("Library selection table - text: " + warehouse.drawTextFeatures());
            VPFLayer.logger.fine("Library selection table - areas: " + warehouse.drawAreaFeatures());
            VPFLayer.logger.fine("Warehouse: " + warehouse);
            VPFLayer.logger.fine("Warehouse: cutoff scale " + BROWSE_CUTOFF);
        }

        // handle Dateline
        if ((scale < BROWSE_CUTOFF) && (ll1.getLongitude() > ll2.getLongitude())) {
            drawTile(scale, screenwidth, screenheight, covname, warehouse, ll1, new LatLonPoint.Float(ll2.getLatitude(), 180f - .00001f)/*
                                                                                                                                         * 180
                                                                                                                                         * -
                                                                                                                                         * epsilon
                                                                                                                                         */);
            drawTile(scale, screenwidth, screenheight, covname, warehouse, new LatLonPoint.Float(ll1.getLatitude(), -180f), ll2);
            return;
        }

        if (VPFLayer.logger.isLoggable(Level.FINE)) {
            VPFLayer.logger.fine("LST.drawTile() with scale of " + scale);
        }

        float dpplat = Math.abs((ll1.getLatitude() - ll2.getLatitude()) / screenheight);
        float dpplon = Math.abs((ll1.getLongitude() - ll2.getLongitude()) / screenwidth);

        int inArea = 0;
        CoverageTable redrawUntiled = null;

        for (CoverageAttributeTable cat : CATs.values()) {

            if (VPFLayer.logger.isLoggable(Level.FINE)) {
                VPFLayer.logger.fine("LST: checking library: " + cat.getLibraryName());
            }

            if (!warehouse.checkLibraryForUsage(cat.getLibraryName())) {
                continue;
            }

            warehouse.resetForCAT();

            List<TileDirectory> tiles = cat.tilesInRegion(ll1.getLatitude(), ll2.getLatitude(), ll2.getLongitude(), ll1.getLongitude());

            if (tiles == null) {
                redrawUntiled = cat.getCoverageTable(covname);
            } else if (cat.isTiledData() && (scale < BROWSE_CUTOFF)) {

                if (!tiles.isEmpty()) {
                    CoverageTable c = cat.getCoverageTable(covname);
                    if (c == null) {
                        if (VPFLayer.logger.isLoggable(Level.FINE)) {
                            VPFLayer.logger.fine("|LST.drawTile(): Couldn't get coverage table for "
                                    + covname + " " + cat.getLibraryName());
                        }
                        continue;
                    }

                    if (VPFLayer.logger.isLoggable(Level.FINE)) {
                        VPFLayer.logger.fine("Using coverage table for " + covname + " "
                                + cat.getLibraryName());
                    }

                    inArea++;
                    for (TileDirectory tileDirectory : tiles) {
                        c.drawTile(tileDirectory, warehouse, ll1, ll2, dpplat, dpplon);
                    }
                }
            }
        }
        if ((redrawUntiled != null) && (inArea == 0)) {
            if (VPFLayer.logger.isLoggable(Level.FINE)) {
                VPFLayer.logger.fine("LST drawing untiled browse data");
            }
            redrawUntiled.drawTile(new TileDirectory(), warehouse, ll1, ll2, dpplat, dpplon);
        }
    }

    /**
     *  
     */
    public void drawFeatures(int scale, int screenwidth, int screenheight, String covname,
                             VPFFeatureWarehouse warehouse, LatLonPoint ll1, LatLonPoint ll2) {

        if (VPFLayer.logger.isLoggable(Level.FINE)) {
            VPFLayer.logger.fine("LST.drawFeatures(): Coverage name: " + covname);
            VPFLayer.logger.fine("Library selection table - edges: " + warehouse.drawEdgeFeatures());
            VPFLayer.logger.fine("Library selection table - text: " + warehouse.drawTextFeatures());
            VPFLayer.logger.fine("Library selection table - areas: " + warehouse.drawAreaFeatures());
            VPFLayer.logger.fine("Warehouse: " + warehouse);
        }

        // handle Dateline
        if ((scale < BROWSE_CUTOFF) && (ll1.getLongitude() > ll2.getLongitude())) {
            drawFeatures(scale, screenwidth, screenheight, covname, warehouse, ll1, new LatLonPoint.Float(ll2.getLatitude(), 180f - .00001f)// 180-epsilon
            );
            drawFeatures(scale, screenwidth, screenheight, covname, warehouse, new LatLonPoint.Float(ll1.getLatitude(), -180f), ll2);
            return;
        }

        if (VPFLayer.logger.isLoggable(Level.FINE)) {
            VPFLayer.logger.fine("LST.drawFeatures() with scale of " + scale);
        }

        float dpplat = Math.abs((ll1.getLatitude() - ll2.getLatitude()) / screenheight);
        float dpplon = Math.abs((ll1.getLongitude() - ll2.getLongitude()) / screenwidth);

        int inArea = 0;
        CoverageTable redrawUntiled = null;

        for (CoverageAttributeTable cat : CATs.values()) {

            if (!warehouse.checkLibraryForUsage(cat.getLibraryName())) {
                continue;
            }

            if (cat.isTiledCoverage() && scale < BROWSE_CUTOFF) {

                CoverageTable c = cat.getCoverageTable(covname);
                if (c == null) {
                    if (VPFLayer.logger.isLoggable(Level.FINE)) {
                        VPFLayer.logger.fine("LST.getFeatures(): Couldn't get coverage table for "
                                + covname + " " + cat.getLibraryName());
                    }
                    continue;
                }

                if (VPFLayer.logger.isLoggable(Level.FINE)) {
                    VPFLayer.logger.fine("Using coverage table for " + covname + " "
                            + cat.getLibraryName());
                }

                c.drawFeatures(warehouse, ll1, ll2, dpplat, dpplon);
                inArea++;
            } else {
                // Set up to draw browse coverage, or non-tiled coverage
                if (VPFLayer.logger.isLoggable(Level.FINE)) {
                    VPFLayer.logger.fine("LST.drawTile(): Scale too small (probably) or no tiles in region.");
                }
                redrawUntiled = cat.getCoverageTable(covname);

                if (redrawUntiled != null) {
                    redrawUntiled.drawFeatures(warehouse, ll1, ll2, dpplat, dpplon);
                }
            }
        }

        // Moved this code up into the redrawUntiled section directly above
        // this. It looks like the code wanted to restrict how many un-tiled
        // coverage attribute tables would be consulted, but for certain kinds
        // of VPF data that gets distributed as a bunch of little libraries,
        // this seems to limit rendering to only one small area.

        // if ((redrawUntiled != null) && (inArea == 0)) {
        // redrawUntiled.drawFeatures(warehouse, ll1, ll2, dpplat, dpplon);
        // }
    }

    /**
     * Given a string for a coverage type or feature type, return the
     * description for that string. Return null if the code string isn't found.
     * 
     * @param coverageOrFeatureType string ID for coverage or Feature type.
     */
    public String getDescription(String coverageOrFeatureType) throws FormatException {
        boolean DEBUG = VPFLayer.logger.isLoggable(Level.FINE);

        if (DEBUG)
            VPFLayer.logger.fine("LST.getDescription: " + coverageOrFeatureType);

        for (String libraryName : getLibraryNames()) {
            CoverageAttributeTable cat = getCAT(libraryName);
            if (cat == null) {
                continue;
            }
            String[] coverages = cat.getCoverageNames();
            for (int j = 0; j < coverages.length; j++) {
                String covname = coverages[j];
                if (coverageOrFeatureType.equalsIgnoreCase(covname)) {
                    if (DEBUG)
                        VPFLayer.logger.fine("** Matches coverage " + covname);
                    return cat.getCoverageDescription(covname);
                } else {
                    if (DEBUG)
                        VPFLayer.logger.fine("   Checking in coverage table " + covname);
                    CoverageTable ct = cat.getCoverageTable(covname);
                    Hashtable<String, CoverageTable.FeatureClassRec> info = ct.getFeatureTypeInfo();
                    for (CoverageTable.FeatureClassRec fcr : info.values()) {
                        String name = fcr.feature_class;
                        if (coverageOrFeatureType.equalsIgnoreCase(name)) {
                            if (DEBUG)
                                VPFLayer.logger.fine("** Found feature " + name);
                            return fcr.description;
                        }
                        if (DEBUG)
                            VPFLayer.logger.fine("   checked " + name);
                    }
                }
            }
        }
        if (DEBUG)
            VPFLayer.logger.fine("-- No matches found.");
        return null;
    }

    /**
     * Just a test main to parse vpf datafiles param args files to parse, plus
     * other command line flags
     * 
     * @param args command line arguments args[0] is a path to the VPF root
     */
    public static void main(String[] args) {
        String dcwbase = null;
        if (args.length > 0) {
            dcwbase = args[0];

            try {
                LibrarySelectionTable lst = new LibrarySelectionTable(dcwbase);
                System.out.println("Database Name " + lst.getDatabaseName());

                for (String libraryName : lst.getLibraryNames()) {
                    System.out.println("Library " + libraryName);
                    lst.getCAT(libraryName);
                }
            } catch (FormatException f) {
                System.err.println("*****************************************");
                System.err.println("*---------------------------------------*");
                System.err.println("Format error in dealing with LST");
                System.err.println(f.getMessage());
                System.err.println("*---------------------------------------*");
                System.err.println("*****************************************");
            }
        } else {
            System.out.println("Need a path to the VPF lat. file");
        }
    }
}